#pragma once
#include "RationalNumber.h"
#include "SimulationTime.h"
#include "Space.h"
#include "Agent.h"
#include "RandomHelper.h"
#include <string>
#include <map>
#include <vector>
#include <list>
#include <queue>
/*
  Copyright 2008 by Alexey Solovyev and University of Pittsburgh
  Licensed under the Academic Free License version 3.0
  See the file "LICENSE" for more information
 */

namespace org
{
	namespace spark
	{
		namespace core
		{


			using org::spark::data::DataLayer;
			using org::spark::math::RationalNumber;
			using org::spark::core::Agent;
			using org::spark::math::SimulationTime;
			using org::spark::space::Space;
			using org::spark::math::RandomHelper;

			//using com::spinn3r::log5j::Logger;

			/// <summary>
			/// Basic class for representing the context. All agents should belong to the
			/// context. This class is abstract. There are several other classes implementing
			/// all methods of the generic context.
			/// </summary>
			/// <seealso cref= org.spark.core.Observer1 </seealso>
			/// <seealso cref= org.spark.core.Observer2 </seealso>
			class Observer
			{
			private:
				class ModelSimulationTime : public SimulationTime
				{
				private:
					//static const long long serialVersionUID = 6151289661321800193LL;

				public:
					ModelSimulationTime(ModelSimulationTime *time);

					ModelSimulationTime();

				
					virtual void advanceTick();

					virtual void setTime(RationalNumber *t);

					virtual void reset();
				};

			public:
				class AgentType 
				{
					/* Type */
				public:
					template <class T>
					T *type;
					/* Time step */
					RationalNumber *timeStep;
					/* Priority */
					int priority;


					/// <summary>
					/// Default constructor </summary>
					/// <param name="type"> </param>
					/// <param name="timeStep"> </param>
					/// <param name="priority"> </param>
					template <class T>
					AgentType(T *type, RationalNumber *timeStep, int priority);

					/// <summary>
					/// Compares priorities: low number means high priority
					/// If priorities are equal, then use lexical ordering
					/// </summary>
					virtual int compareTo(AgentType *b);
				};

			private:
				class AgentTime 
				{
				public:
					RationalNumber *time;
					AgentType *type;

					/// <summary>
					/// Default constructor </summary>
					/// <param name="type"> </param>
					AgentTime(AgentType *type);

					virtual int compareTo(AgentTime *t);


					virtual void advanceTime();

				};

			public:
				class MyObjectInputStream 
				{
				protected:
					//ClassLoader *cl;
					bool userClass;

				public:
					//MyObjectInputStream(InputStream *in_Renamed); //throw(Exception);

					virtual void setUserClass(bool flag);

				protected:
					//virtual Class *resolveClass(ObjectStreamClass *desc); //throw(IOException, ClassNotFoundException);

				private:
					void InitializeInstanceFields();
				};
				// Log
			private:
				//static Logger *const logger;

				/// <summary>
				/// Priority constants
				/// </summary>
			public:
				static const int HIGH_PRIORITY = 0;
				static const int LOW_PRIORITY = 1000;
				void setObserver(Observer *observer);
				// Instance
				// TODO: should be removed later
				static volatile Observer *instance;


				// Simulation time class

				// Implementation of main methods
			    static volatile Observer *impl;
			private:
				//ObserverImpl *impl;
				// Mode execution flag
			//	private static volatile boolean serialMode;
				// TODO: make it final and add constructor
				// Observers should be created for each loaded model
				int executionMode;

				/* True when inside setup method */
				bool setupFlag;


				/// <summary>
				/// Returns true if setup method is executed
				/// @return
				/// </summary>
			public:
				bool getSetupFlag();


				/// <summary>
				/// Description of time properties of an agent type
				/// @author Monad
				/// </summary>

				// All registered agent types
			private:
				const std::map<std::string, AgentType*> agentTypes;


				/// <summary>
				/// Represents a time event for the given type of agents
				/// @author Monad
				/// 
				/// </summary>
				// The queue of agent actions
				std::priority_queue<AgentTime*> *const actionQueue;

				// Simulation time
				ModelSimulationTime *const time;

				/* List of all spaces */
				const std::vector<Space*> spacesList;
				/* Hash map of all spaces */
				const std::map<std::string, Space*> spacesMap;

				// Default space
				Space *defaultSpace;

				// TODO: be careful with agents which are added and then removed in the
				// same step
				// New agents queue
				// TODO: ObserverParallel can use ParrallelArray for new agents also
				// TODO: maybe ArrayList is better?
				const std::map<std::string, std::vector<Agent*>> newAgents;
				const std::vector<Agent*> removedQueue;

				// for some internal statistics
				long long statTime;

				// List of user commands 
				// TODO: (experimental)
				//const std::list<ControlState::KeyEvent*> keyEvents;
				//const std::list<ControlState::MouseEvent*> mouseEvents;

				//ControlState::KeyState *const keyState;
				//ControlState::MouseState *const mouseState;


				/// <summary>
				/// Returns the current simulation time
				/// @return
				/// </summary>
			public:
				SimulationTime *getSimulationTime();


				/// <summary>
				/// Returns the current time value
				/// @return
				/// </summary>
				RationalNumber *getTime();


				/// <summary>
				/// Returns the current execution mode
				/// @return
				/// </summary>
				int getExecutionMode();


				/// <summary>
				/// Returns the current simulation tick
				/// @return
				/// </summary>
				long long getSimulationTick();


				/// <summary>
				/// Advances simulation tick by one
				/// </summary>
				void advanceSimulationTick();


				/// <summary>
				/// Returns true if serial mode is on
				/// @return
				/// </summary>
				bool isSerial();



				/// <summary>
				/// Internal constructor
				/// </summary>
				Observer(Observer *implementation, int executionMode);

				/// <summary>
				/// Gets the instance of the observer
				/// </summary>
				/// <returns> observer an instance of the observer </returns>
				static Observer *getInstance();

				/// @deprecated Gets the instance of the space 
				/// <returns> space a current model space </returns>
				static Space *getSpace();

				/// <summary>
				/// Gets the instance of a default space
				/// 
				/// @return
				/// </summary>
				static Space *getDefaultSpace();

				/// <summary>
				/// Returns the instance of space with a given name
				/// </summary>
				/// <param name="name">
				/// @return </param>
				static Space *getSpace(const std::string &name);


				/// <summary>
				/// Adds a key event into a queue </summary>
				/// <param name="cmd"> </param>
				//void addKeyEvent(ControlState::KeyEvent *keyEvent);

				/// <summary>
				/// Adds a mouse event into a queue </summary>
				/// <param name="mouseEvent"> </param>
				//void addMouseEvent(ControlState::MouseEvent *mouseEvent);


				/// <summary>
				/// Returns the keyboard state
				/// @return
				/// </summary>
				//ControlState::KeyState *getKeyState();


				/// <summary>
				/// Returns the mouse state
				/// @return
				/// </summary>
				//ControlState::MouseState *getMouseState();


				/// <summary>
				/// Returns the next keyboard event 
				/// @return
				/// </summary>
				//ControlState::KeyEvent *nextKeyEvent();

				/// <summary>
				/// Returns the next mouse event
				/// @return
				/// </summary>
				//ControlState::MouseEvent *nextMouseEvent();


				/// <summary>
				/// Clears the context by removing all agents, data layers, etc.
				/// </summary>
			/*	public void clear() {
					newAgents.clear();
			
					defaultSpace = null;
					spacesList.clear();
					spacesMap.clear();
			
					actionQueue.clear();
					time.reset();
					
					agentTypes.clear();
					
					impl.clear();
			
					logger.debug("Resetting random generator");
					RandomHelper.reset();
				}
			*/	

				/// <summary>
				/// Resets the Observer
				/// Does not affect the information about agent types
				/// </summary>
				void reset();

				/// <summary>
				/// Removes all agents from the context
				/// </summary>
				void clearAgents();


				/// <summary>
				/// Initializes the action queue
				/// </summary>
			private:
				void initializeActionQueue();


				/// <summary>
				/// Creates a definition of the specific type of agents </summary>
				/// <param name="type"> </param>
				/// <param name="time"> </param>
				/// <param name="priority"> </param>
			public:
				void setAgentType(std::string *type, RationalNumber *timeStep, int priority);


				/// <summary>
				/// Creates a definition of the specific type of agents
				/// with default parameters </summary>
				/// <param name="type"> </param>
				void setAgentType(std::string type);

				/// @deprecated Sets up the space for the model. This function should be
				///             called before all other steps of the model initialization. It
				///             can be called only once 
				void setSpace(Space *space);

				/// <summary>
				/// Adds a new space to the context
				/// </summary>
				/// <param name="space"> </param>
// There is no native C++ template equivalent to generic constraints:
				template<typename T> 
				T addSpace(const std::string &name, T space);

				/// <summary>
				/// Returns names of all spaces in a model
				/// 
				/// @return
				/// </summary>
				std::string *getSpaceNames();

				/// <summary>
				/// Returns the name associated with a given space
				/// </summary>
				/// <param name="space"> </param>
				/// <returns> null if no such space found </returns>
				std::string getSpaceName(Space *space);

				/// <summary>
				/// Sets a default space for spatial operations
				/// </summary>
				/// <param name="name"> </param>
				void setDefaultSpace(const std::string &name);

				/// @deprecated Adds a new data layer to a default space 
				/// <param name="name">
				///            a name of a new data layer </param>
				/// <param name="data">
				///            a data layer itself </param>
// There is no native C++ template equivalent to generic constraints:
				template<typename T> 
				T addDataLayer(const std::string &name, T data);

				/// @deprecated Use space.getDataLayer(name) or findDataLayer(name) instead.
				///             Gets the specific data layer 
				/// <param name="name">
				///            a name of the data layer
				/// @return </param>
				DataLayer *getDataLayer(const std::string &name);

				/// <summary>
				/// Finds the specific data layer in a model
				/// </summary>
				/// <param name="name"> </param>
				/// <returns> null if no data layer was found </returns>
				DataLayer *findDataLayer(const std::string &name);

				/// <summary>
				/// Adds the agent into the context
				/// </summary>
				/// <param name="agent">
				///            an agent to be added to the context </param>
			protected:

				/// <summary>
				/// Adds an agent into the context instantly without any waiting queues
				/// </summary>
				/// <param name="agent"> </param>
			public:
				void addAgentInstantly(Agent *agent);
				void addAgent(Agent *agent);
				void removeAgent(Agent *agent);
				/// <summary>
				/// Removes the agent from the context
				/// </summary>
				/// <param name="agent"> </param>
			protected:
				

				/// <summary>
				/// Returns the number of specific agents
				/// </summary>
				/// <param name="type">
				///            a type of agents for which the number is retrieved </param>
				/// <returns> the number of agents of the given type </returns>
			public:
				template<typename T>
				int getAgentsNumber(T type);

				/// <summary>
				/// Returns the number of agents derived from a specific type
				/// </summary>
				/// <param name="type">
				/// @return </param>
				template<typename T>
				int getAgentsNumberOfKind(T type);

				/// <summary>
				/// Processes a queue of newly added agents
				/// </summary>
			private:
				void processNewAgents();

				/// <summary>
				/// Processes a queue of removed agents
				/// </summary>
				void processRemovedAgents();

				/// <summary>
				/// @deprecated
				/// Processes all agents
				/// </summary>
				/// <param name="tick">
				///  			a number representing time passed since the model start </param>
			public:
				void processAllAgents(long long tick);



			private:
				std::vector<AgentType*> tempTypeList;

				/// <summary>
				/// Fills in the type list </summary>
				/// <param name="t1"> </param>
				void fillTypeList(RationalNumber *t1);
				/// <summary>
				/// Processes all agents in the time interval [t, t + dt]
				/// where t is the current time </summary>
				/// <param name="dt"> </param>
			public:
				void processAllAgents(RationalNumber *dt);


				/// <summary>
				/// Call before model setup method
				/// </summary>
				void beginSetup();

				/// <summary>
				/// Call after model setup method
				/// </summary>
				void finalizeSetup();

				/// <summary>
				/// Processes all data layers
				/// </summary>
				/// <param name="tick">
				///            a number representing time passed since the model start </param>
				void processAllDataLayers(long long tick);

				// TODO: should be protected or remove this method at all
				// it is used only in the render and internally by the observer
				/// <summary>
				/// Returns a set of specific agents
				/// </summary>
				/// <param name="type">
				///            a type of agents to be returned </param>
				/// <returns> a set of all agents of the given type </returns>
// There is no native C++ template equivalent to generic constraints:
				template<typename T> 
				T *getAgents(T *type);

				/// @deprecated Returns all agents
				/// @return 
				Agent *getAgents();

				/// <summary>
				/// Returns a list of agents of a specific type
				/// </summary>
				/// @param <T> </param>
				/// <param name="type">
				/// @return </param>
// There is no native C++ template equivalent to generic constraints:
				template<typename T> 
				std::vector<T*> getAgentsList(T *type);

				/// <summary>
				/// Returns all agents derived from a specific type
				/// </summary>
				/// @param <T> </param>
				/// <param name="type">
				/// @return </param>
// There is no native C++ template equivalent to generic constraints:
				template<typename T>
				std::vector<T> getAgentsListOfKind(T *type);

				/// @deprecated Returns the names of data layers in all spaces 
				/// <returns> names of all data layers </returns>
				std::string *getDataLayers();

				/// <summary>
				/// Prints performance statistics
				/// </summary>
				void printStatistics();

				/// <summary>
				/// Writes the current state of the model into the output stream
				/// </summary>
				/// <param name="model"> </param>
				/// <param name="out"> </param>
				// FIXME: serialize time and actionQueue with agentTypes
				// FIXME: save observer itself (executionMode, etc.) and its implementation (type)
				//void serializeState(SparkModel *model, OutputStream *out) //throw(Exception);
				template<typename T>
				std::string getType(T *t);
				/// <summary>
				/// Loads the model state from the input stream
				/// </summary>
				/// <param name="model"> </param>
				/// <param name="in"> </param>
				// TODO: make static + load observer itself and its implementation
				//static void loadState(SparkModel *model, InputStream *in_Renamed, ClassLoader *cl) //throw(Exception);
			template<typename T>
			inline
			std::string Observer::getType(T *t)
			{
				return typeid(*t).name();
			}

			private:
				void InitializeInstanceFields();
			};

			

		}
	}
}
